/**
 * @file Define WeChat service
 * 
 * @author Steve Gao<gaofu@parllay.com>
 */
const async                 = require('async');
const jssha                 = require('jssha');

const utils                 = require('../utils');
const redisKey              = require('../redisKey');
const BaseService           = require('./base-service').BaseService;
const WeChatAPI             = require('../apis/wechat-api').WeChatAPI;
const WeChatMessageHelper   = require('../helpers/wechat-helpers').WeChatMessageHelper;

const {SystemServices, Configuration}        = require('../constants');

const {WeChatError, RedisError}         = require('../errors');

/**
 * The WeChat Service wrap all functionalities for WeChat
 * 
 * @class WeChatService
 */
class WeChatService extends BaseService {

    /**
     * The construction function
     * 
     * @param {ApplicationContext} context - The application context
     */
    constructor(context) {
        super(context);
        //TODO: Move the schedule code into our schedule server
        this.setupClientAccessTokenSchedule();
    }

    get wallService() {
        return this.getSystemService(SystemServices.WALL_SERVICE);
    }

    /**
     * The wechat configuration information
     */
    get WeChatConfig() {
        return this.getApplicationConfiguration(Configuration.WECHAT);
    }

    /**
     * Refresh the Official Account's access token for our third-party application
     * 
     * @name refreshPlatformAccessToken
     * @param {String} appId - The Official Account's application id
     * @param {String} verifyTicket - The verity ticket from the WeChat
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    refreshPlatformAccessToken(appId, verifyTicket, parentCallback) {
        var me = this;
        this.acquireStorageClients({
            redis: true
        }, function(clients) {
            var redis = clients.redis;
            var platformKey = redisKey.platform_auth(appId);
            var preAuthCodeKey = redisKey.platform_pre_auth_code(appId);
            var authInfo = null;
            async.waterfall([
                function(callback) {
                    redis.hgetall(platformKey, callback);
                },
                function(value, callback) {
                    var needRenew = false;
                    if(value == null) {
                        needRenew = true;
                    }
                    if(value) {
                        var now = Date.now();
                        var diff = now - value.timestamp;
                        if(diff >= 6300000 || Number.isNaN(diff)) {
                            needRenew = true;
                        }
                        authInfo = value;
                    }
                    callback(null, needRenew);
                },
                function(needRenew, callback) {
                    if(needRenew) {
                        WeChatAPI.getPlatformAccessToken({
                            'component_appid' : me.WeChatConfig.component_app_id,
                            'component_appsecret' : me.WeChatConfig.component_app_secret,
                            'component_verify_ticket' : verifyTicket
                        }, function(data) {
                            if (data && data.component_access_token) {
                                callback(null, data.component_access_token, verifyTicket);
                            } else {
                                if (data && data.errno) {
                                    me.reportError(new WeChatError(data.errmsg, data.errno, {
                                        app_id: appId,
                                        verify_ticket: verifyTicket
                                    }));
                                }
                                callback(null, null, verifyTicket);
                            }
                        });
                    } else {
                        callback(null, null, verifyTicket);
                    }
                }, function(token, ticket, callback) {
                    if(ticket) {
                        var multi = redis.multi();
                        if(token) {
                            multi.hset(platformKey, 'token', token);
                            multi.hset(platformKey, 'timestamp', Date.now());
                            multi.del(preAuthCodeKey);
                        }
                        multi.hset(platformKey, 'ticket', ticket);
                        /*if(token) {
                            me.getPlatformPreAuthCode(appId, token, function(code) {
                                
                                console.log(code);
                            });
                        }*/
                        multi.exec(callback);
                    } else {
                        me.releaseStorageClients(clients);
                        parentCallback('success');
                    }
                }, function(result) {
                    me.releaseStorageClients(clients);
                    parentCallback('success');
                }
            ]);
        }, function(message) {
            parentCallback('success');
        });
    }

    /**
     * Get the oauth url for official account
     * 
     * @name getPlatformOAuthURL
     * @param {String} redirectUri - The redirect uri after OAuth
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    getPlatformOAuthURL(redirectUri, parentCallback) {
        var me = this;
        async.parallel({
            redis: function(callback) {
                me.Context.acquireRedisClient(callback);
            }
        }, function(error, result) {
            if(error) {
                return parentCallback('FAILED');
            }
            
            var redis = result.redis;
            var appId = me.WeChatConfig.component_app_id;
            var preAuthCodeKey = redisKey.platform_pre_auth_code(appId);
            var platformKey = redisKey.platform_auth(appId);
            async.waterfall([
                function(callback) {
                    redis.get(preAuthCodeKey, callback);
                },
                function(code, callback) {
                    if(code) {
                        callback(null, code);
                    } else {
                        redis.hgetall(platformKey, callback);
                    }
                }, 
                function(authInfo, callback) {
                    if(!authInfo) {
                        me.Context.releaseRedisClient(redis);
                        parentCallback('FAILED');
                    } else {
                        if(typeof authInfo === 'object') {
                            me.getPlatformPreAuthCode(appId, authInfo.token, function(code) {
                                if(code === 'FAILED') {
                                    redis.hset(platformKey, 'timestamp', 0, function(error, result) {
                                        me.refreshPlatformAccessToken(appId, authInfo.ticket, function() {
                                            console.log(arguments);
                                        });
                                        me.Context.releaseRedisClient(redis);    
                                    });
                                    
                                    parentCallback(code);
                                } else {
                                    callback(null, code, true);
                                }
                            });
                        } else {
                            callback(null, authInfo, false);
                        }
                    }
                },
                function(code, expired) {
                    if(expired) {
                        var multi = redis.multi();
                        multi.set(preAuthCodeKey, code);
                        multi.expire(preAuthCodeKey, 10);
                        multi.exec(function() {
                            me.Context.releaseRedisClient(redis);
                        });
                    } else {
                        me.Context.releaseRedisClient(redis);
                    }
                    var url = WeChatAPI.getPlatformOAuthURL(appId, code, redirectUri);
                    parentCallback(url);
                }
            ]);
        });
    }
    
    /**
     * Get platform pre_auth_code
     * 
     * @name getPlatformPreAuthCode
     * @param {String} componentAppId - The third-party application id
     * @param {String} componentAccessToken - The third-party application access token
     * 
     * @returns {void}
     */
    getPlatformPreAuthCode(componentAppId, componentAccessToken, parentCallback) {
        var me = this;
        WeChatAPI.getPlatformPreAuthCode({
            component_app_id: componentAppId,
            token: componentAccessToken
        }, function(error, data) {
            if (error || data.errno) {
                me.emit('wechat_error');
                //TODO: Add log here
                parentCallback('FAILED');
            } else {
                if(data && data.pre_auth_code) {
                    parentCallback(data.pre_auth_code);
                } else {
                    parentCallback('FAILED');
                }
            }
        });
    }

    /**
     * Get WeChat fans' OAuth url
     * 
     * @name getWeChatUserOAuthUrl
     * @param {String} appId - The offical account's application id
     * @param {Request} request - The request object
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    getWeChatUserOAuthUrl(appId, request, parentCallback) {
        var redirectUri = `http://${request.headers['host']}/wall/${request.params.wall_id}/authorize`;
        var redirectUri = "http://" + request.headers['host'] + "/wall/" + request.params.wall_id + "/authorize";
        var me = this;
        async.waterfall([
            function(callback) {
                me.wallService.getAccountByAppId1(appId, 'wechat', function(account) {
                    callback(null, account);
                });
            },
            function(account, callback) {
                if(account.id) {
                    switch(account.type) {
                    case 'normal':
                        parentCallback(WeChatAPI.getWeChatUserOAuthUrl(appId, redirectUri, null, null));
                        break;
                    case 'oauth':
                        if(Number(account.service_type) === 2) {
                            parentCallback(WeChatAPI.getWeChatUserOAuthUrl(appId, redirectUri, null, me.WeChatConfig.component_app_id));
                        } else {
                            parentCallback('anonymous');
                        }
                        break;
                    }
                } else {
                    callback('/404')
                }
            }
        ])
    }

    get refreshTimerId() {
        return this._refreshTimerId;
    }
    
    get isInRefreshing() {
        return this._isInRefreshing;
    }

    /**
     * Setup the schdule for refreshing the offical account's access token
     * 
     * @name setupClientAccessTokenSchedule
     * 
     * @returns {void}
     */
    setupClientAccessTokenSchedule() {
        if(this.refreshTimerId) {
            return;
        }
        var me = this;
        this._refreshTimerId = setInterval(function() {
            me.refreshClientAccessTokens();
        }, this.WeChatConfig.token_refresh_interval);
    }

    /**
     * Refresh all offical accounts' access token when the schedule start
     * 
     * @name refreshClientAccessTokens
     * 
     * @returns {void}
     */
    refreshClientAccessTokens() {
        if(this.isInRefreshing) {
            return;
        }
        this._isInRefreshing = true;
        var me = this;
        async.waterfall([
            function(callback) {
                me.wallService.getAllOfficialAccounts(function(data) {
                    if(data.length == 0) {
                        me._isInRefreshing = false;
                    }
                    callback(null, data);
                }, true);
            }, function(accounts) {
                me.doRefreshAccessTokens(accounts);
            }
        ]);
    }

    /**
     * Implement the logic to do the refresh access token task
     * 
     * @name doRefreshAccessTokens
     * @param {Array} accounts - The array of offical accounts
     * 
     * @returns {void}
     */
    doRefreshAccessTokens(accounts) {
        var me = this;
        async.parallel({
            redis: function(callback) {
                me.Context.acquireRedisClient(callback);
            }
        }, function(error, result) {
            if(error) {
                me._isInRefreshing = false;
                return;
            }
            
            var redis = result.redis;
            var componentAppId = me.WeChatConfig.component_app_id;
            var platformKey = redisKey.platform_auth(componentAppId);
            async.waterfall([
                function(callback) {
                    var multi = redis.multi();
                    accounts.forEach(function(account) {
                        var clientAuthKey = redisKey.client_auth(account.app_id);
                        multi.hgetall(clientAuthKey);
                    });
                    multi.exec(callback);
                }, function(result, callback) {
                    var retValue = [];
                    var now = Date.now();
                    if(result && result.length > 0) {
                        for (var index = 0; index < result.length; index++) {
                            var elt = result[index];
                            if(!elt || now - elt.timestamp > 6600000 ) {
                                retValue.push(accounts[index]);
                            } 
                        }
                    }
                    callback(null, retValue);
                }, function(accounts) {
                    var functionChain = [];
                    accounts.forEach(function(account) {
                        var now = Date.now();
                        var clientAuthKey = redisKey.client_auth(account.app_id);
                        switch(account.type) {
                        case 'normal':
                            functionChain.push(function(parentCallback) {
                                async.waterfall([
                                    function(callback) {
                                        WeChatAPI.getAccessToken({
                                            appid: account.app_id,
                                            secret: account.access_token,
                                            grant_type: 'client_credential'
                                        }, function(data) {
                                            callback(null, data && data.access_token);
                                        });
                                    },
                                    function(token, callback) {
                                        if(token) {
                                            WeChatAPI.getApiTicket({
                                                access_token : token,
                                                type : "jsapi"
                                            }, function(data) {
                                                if(data) {
                                                    callback(null, token, data.ticket);
                                                } else {
                                                    parentCallback(null);
                                                }
                                            });
                                        } else {
                                            parentCallback(null);
                                        }
                                    }, function(token, ticket, callback) {
                                        var multi = redis.multi();
                                        multi.hset(clientAuthKey, 'token', token);
                                        multi.hset(clientAuthKey, 'ticket', ticket);
                                        multi.hset(clientAuthKey, 'timestamp', now);
                                        multi.exec(callback);
                                    }, function() {
                                        parentCallback(null);
                                    }
                                ]);
                            });
                            break;
                        case 'oauth':
                            functionChain.push(function(parentCallback) {
                                async.waterfall([
                                    function(callback) {
                                        redis.hgetall(platformKey, callback);
                                    },
                                    function(authInfo, callback) {
                                        console.log(`${account.app_id}: ${account.refresh_token}`);
                                        if(authInfo) {
                                            WeChatAPI.refreshAuthorizerAccessToken(componentAppId, authInfo.token, account.app_id, account.refresh_token, function(error, data) {
                                                if(data && data.authorizer_access_token) {
                                                    callback(null, data.authorizer_access_token, data.authorizer_refresh_token);
                                                } else {
                                                    me.logger.error(`FAILED to refresh token of ${account.app_id}: ${JSON.stringify(error || data)}`);
                                                    parentCallback(null);
                                                }
                                            });
                                        } else {
                                            parentCallback(null);
                                        }
                                    },
                                    function(token, refresh_token, callback) {
                                        WeChatAPI.getApiTicket({
                                            access_token : token,
                                            type : "jsapi"
                                        }, function(data) {
                                            if(data) {
                                                callback(null, token, refresh_token, data.ticket);
                                            } else {
                                                parentCallback(null);
                                            }
                                        });
                                    }, function(token, refresh_token, ticket, callback) {
                                        me.wallService.updateAccountAuthInfo(account.app_id, token, refresh_token, function(data) {
                                            var multi = redis.multi();
                                            multi.hset(clientAuthKey, 'token', token);
                                            multi.hset(clientAuthKey, 'ticket', ticket);
                                            multi.hset(clientAuthKey, 'timestamp', now);
                                            multi.exec(callback);
                                        });
                                    }, function() {
                                        parentCallback(null);
                                    }
                                ])
                            });
                            break;
                        }
                    });
                    functionChain.push(function() {
                        me._isInRefreshing = false;
                        me.Context.releaseRedisClient(redis);
                    });
                    async.waterfall(functionChain);
                }
            ]);
        });
    }

    /**
     * Get WeChat JSSDK config
     * 
     * @name getWeChatJssdkConfig
     * @param {String} appId - The offical account's application id
     * @param {Request} request - The request object
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    getWeChatJssdkConfig(appId, request, parentCallback) {
        var me = this;
        var clientAuthKey = redisKey.client_auth(appId);
        var redis = null;
        async.waterfall([
            function(callback) {
                me.Context.acquireRedisClient(callback)
            },
            function(redisClient, callback) {
                redis = redisClient;
                if(redis) {
                    redis.hgetall(clientAuthKey, callback);
                } else {
                    parentCallback({});
                }
            },
            function(authInfo, callback) {
                var url = "http://" + request.headers['host'] + request.originalUrl;
                var createNonceStr = function () {
                    return Math.random().toString(36).substr(2, 15);
                };

                var createTimestamp = function () {
                    return parseInt(new Date().getTime() / 1000) + '';
                };

                var raw = function (args) {
                    var keys = Object.keys(args);
                    keys = keys.sort()
                    var newArgs = {};
                    keys.forEach(function (key) {
                        newArgs[key.toLowerCase()] = args[key];
                    });

                    var string = '';
                    for (var k in newArgs) {
                        string += '&' + k + '=' + newArgs[k];
                    }
                    string = string.substr(1);
                    return string;
                };
                var ret = {
                    jsapi_ticket: authInfo.ticket,
                    nonceStr: createNonceStr(),
                    timestamp : createTimestamp(),
                    url: url
                };
                var string = raw(ret);
                var shaObj = new jssha(string, 'TEXT');
                var signature = shaObj.getHash('SHA-1', 'HEX');
                
                var config = {
                    appId: appId,
                    timestamp: ret.timestamp,
                    nonceStr: ret.nonceStr,
                    signature: signature
                };
                me.Context.releaseRedisClient(redis);
                parentCallback(config);
            }
        ]);
    }

    /**
     * Handle the WeChat fans' authorization 
     * 
     * @name handleWeChatUserAuthorization
     * @param {String} code - The authroization code
     * @param {String} wallId - The id of wall
     * @param {String} userId - The current user id
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    handleWeChatUserAuthorization(code, wallId, userId, parentCallback) {
        var me = this;
        var userInfo = {};
        var appId = null;
        var authorizerAccessToken = null;
        async.waterfall([
            function(callback) {
                me.wallService.getWallById(wallId, function(wall) {
                    if(!wall) {
                        parentCallback({
                            success: false,
                            error: {
                                errno: 'missing-wall',
                                message: '没有找到访问的朋友圈！'
                            }
                        });
                    } else {
                        callback(null, wall);
                    }
                });
            }, function(wall, callback) {
                me.wallService.getAccountByAppId1(wall.app_id, 'wechat', function(account) {
                    appId = account.app_id;
                    if(account.type === 'normal') {
                        var secret = account.access_token;
                        callback(null, appId, secret, account.type);
                    } else if(account.type === 'oauth') {
                        callback(null, appId, account.access_token, account.type);
                    }
                }, true);
            }, function(appId, secretOrSecret, type, parentCallback2) {
                authorizerAccessToken = secretOrSecret;
                if(type === 'oauth') {
                    async.waterfall([
                        function(callback) {
                            me.Context.acquireRedisClient(callback);
                        }, function(redis) {
                            if(redis) {
                                var platformKey = redisKey.platform_auth(me.WeChatConfig.component_app_id);
                                redis.hgetall(platformKey, function(error, authInfo) {
                                    me.Context.releaseRedisClient(redis);
                                    if (error) {
                                        me.reportError(new RedisError(error.message, '', {
                                            component_app_id: me.WeChatConfig.component_app_id
                                        }));
                                        parentCallback({
                                            success: false,
                                            error: {
                                                errno: 'internal-error',
                                                message: '系统内部错误，请稍后再试！'
                                            }
                                        });
                                    } else {
                                        if(!authInfo) {
                                            me.reportError(new RedisError('authInfo is null', '', {
                                                component_app_id: me.WeChatConfig.component_app_id
                                            }));
                                            parentCallback({
                                                success: false,
                                                error: {
                                                    errno: 'missing-platform-auth-info',
                                                    message: '系统内部错误，请稍后再试！'
                                                }
                                            });
                                        } else {
                                            parentCallback2(null, appId, authInfo.token, type);
                                        }
                                    }
                                });
                            } else {
                                parentCallback({
                                    success: false,
                                    error: {
                                        errno: 'redis-acquire-connection-error',
                                        message: '获取Redis链接出错！'
                                    }
                                });
                            }
                        }
                    ]);
                } else {
                    parentCallback2(null, appId, secretOrSecret, type);
                }
            }, 
            function(appId, secretOrToken, type, callback) {
                var componentAppId = null;
               
                if(type === 'oauth') {
                    componentAppId = me.WeChatConfig.component_app_id;
                }
                WeChatAPI.getWeChatUserAccessToken(appId, secretOrToken, code, componentAppId, function(data) {
                    if(data && data.access_token) {
                        userInfo.refresh_token = data.refresh_token;
                        userInfo.open_id = data.openid;
                        userInfo.union_id = data.unionid;
                        callback(null, data.access_token, userInfo, type);
                    } else {
                        me.reportError(new WeChatError(data && data.errmsg || 'get access token error', data && data.errno || JSON.stringify(data) || 'unknown', {
                            component_app_id: componentAppId,
                            app_id: appId,
                            code: code,
                            secret_or_token: secretOrToken
                        }));
                        parentCallback({
                            success: false,
                            error: {
                                errno: data && data.errno,
                                message: data && data.errmsg
                            }
                        });
                    }
                });
                
            }, function(token, userInfo, type, callback) {
                WeChatAPI.getWeChatUserInfo(authorizerAccessToken, userInfo.open_id, type, function(data) {
                    if(data.openid) {
                        userInfo.id = userId || null;
                        userInfo.province = data.province || '未知';
                        userInfo.open_id = data.openid;
                        userInfo.city = data.city || '未知';
                        userInfo.country = data.country || '未知';
                        userInfo.profile_image = data.headimgurl;
                        userInfo.gender = data.sex || 1;
                        userInfo.name = data.nickname || '匿名用户(微信)';
                        userInfo.privilege = data.privilege || '';
                        userInfo.union_id = data.unionid;
                        userInfo.wall_id = wallId;
                        userInfo.is_anonymous = !data.nickname;
                        callback(null, userInfo);
                    } else {
                        data = data || {};
                        data.wall_id = wallId;
                        data.user_id = userId;
                        data.code = code;
                        me.reportError(new RedisError(data && data.errmsg || 'Get user information error', data && data.errno, data));
                        parentCallback({
                            success: false,
                            error: {
                                errno: data && data.errno,
                                message: data && data.errmsg || 'Get WeChat user information error'
                            }
                        });
                    }
                })
            }, function(userInfo) {
                var isAnonymous = userInfo.is_anonymous;
                me.wallService.saveOrUpdateWallUser(userInfo, function(id) {
                    if(id) {
                        parentCallback({
                            success: true,
                            data: {
                                wall_user_id: id,
                                app_id: appId,
                                is_anonymous: isAnonymous
                            }
                        });
                    } else {
                        me.reportError(new RedisError('Save user information failed', '', id));
                        parentCallback({
                            success: false,
                            error: {
                                errno: 'system-busy',
                                message: '系统内部错误，请稍后再试！'
                            }
                        });
                    }
                });
            }
        ])
    }

    /**
     * Handle the official account's authorization
     * 
     * @name handleWeChatPlatformAuthorization
     * @param {Object} params - The params come from WeChat
     * @param {String} userId - The userId of current session
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    handleWeChatPlatformAuthorization(params, userId, parentCallback) {
        if(!params.auth_code) {
            parentCallback({
                success: false,
                error: '授权失败'
            });
        } else {
            var me = this;
            var componentAppId = this.WeChatConfig.component_app_id;
            var platformKey = redisKey.platform_auth(componentAppId);
            async.parallel({
                redis: (callback) => {
                    me.Context.acquireRedisClient(callback);
                }
            }, (error, result) => {
                if(error) {
                    parentCallback({
                        success: false,
                        error: '系统内部错误，请稍后再试！'
                    });
                    return;
                }
                
                var redis = result.redis;
                async.waterfall([
                    (callback) => {
                        redis.hgetall(platformKey, callback);
                    },
                    (result, callback) => {
                        if(result) {
                            WeChatAPI.getAuthorizerAccessToken(componentAppId, result.token, params.auth_code, function(error, data) {
                                if(data && data.authorization_info) {
                                    callback(null, data.authorization_info, result.token);
                                } else {
                                    me.Context.releaseRedisClient(redis);
                                    parentCallback({
                                        success: false,
                                        error: '系统内部错误，请稍后再试！'
                                    });
                                }
                            });
                        } else {
                            me.Context.releaseRedisClient(redis);
                            parentCallback({
                                success: false,
                                error: '系统内部错误，请稍后再试！'
                            });
                        }
                    },
                    function(data, componentToken, callback) {
                        if(data.authorizer_access_token) {
                            WeChatAPI.getOfficialAccountBaseInfo(componentAppId, componentToken, data.authorizer_appid, function(result) {
                               if(result && result.authorizer_info) {
                                   callback(null, data.authorizer_appid, data.authorizer_access_token, data.authorizer_refresh_token, result);
                               } else {
                                   me.Context.releaseRedisClient(redis);
                                    parentCallback({
                                        success: false,
                                        error: '系统内部错误，请稍后再试！'
                                    });
                               }
                            });
                        } else {
                            me.Context.releaseRedisClient(redis);
                            parentCallback({
                                success: false,
                                error: '系统内部错误，请稍后再试！'
                            });
                        }
                    }, 
                    function(authorizerAppId, authorizerAccessToken, refreshToken, accountInfo, callback) {
                        var account = {
                            user_id: userId,
                            app_id: authorizerAppId,
                            name: accountInfo.authorizer_info.nick_name,
                            logo: accountInfo.authorizer_info.head_img,
                            refresh_token: refreshToken,
                            access_token: authorizerAccessToken,
                            qr_code: accountInfo.authorizer_info.qrcode_url,
                            service_type: accountInfo.authorizer_info.service_type_info.id,
                            verify_type: accountInfo.authorizer_info.verify_type_info.id,
                            type: 'oauth',
                            social_type: 'wechat'
                        };
                        me.wallService.saveOrUpdateAccount(userId, account, function(error, data) {
                            if(error) {
                                me.Context.releaseRedisClient(redis);
                                parentCallback({
                                    success: false,
                                    error: error
                                });
                            } else {
                                parentCallback({
                                    success: true,
                                    data: data
                                });
                                callback(null, authorizerAppId, authorizerAccessToken);
                            }
                        });
                        
                    },
                    function(authorizerAppId, authorizerAccessToken, callback) {
                        WeChatAPI.getApiTicket({
                            access_token : authorizerAccessToken,
                            type : "jsapi"
                        }, function(data) {
                            if(data) {
                                callback(null, authorizerAppId, authorizerAccessToken, data.ticket);
                            } else {
                                callback(null, null, null, null);
                            }
                        });
                    },
                    function(authorizerAppId, authorizerAccessToken, ticket) {
                        if(authorizerAccessToken) {
                            var clientAuthKey = redisKey.client_auth(authorizerAppId);
                            var multi = redis.multi();
                            multi.hset(clientAuthKey, 'token', authorizerAccessToken);
                            multi.hset(clientAuthKey, 'ticket', ticket);
                            multi.hset(clientAuthKey, 'timestamp', Date.now());
                            multi.exec(function() {
                                me.Context.releaseRedisClient(redis);
                            });
                        } else {
                            me.Context.releaseRedisClient(redis);
                        }
                    }
                ])
            });
        }
    }

    /**
     * Wrapper the WeChatAPI's getAuthorizerAccessToken method 
     * 
     * @name getAuthorizerAccessToken
     * @param {String} componentAppId - The WeChat third-party application id
     * @param {String} componentToken - The WeChat third-party application access token
     * @param {String} authorizationCode - The authorization code from WeChat third-party platform
     * @param {Function} callback - The callback function
     * 
     * @returns {void}
     */
    //TODO: need refactor, we just need to provide authorizationCode for parameter
    getAuthorizerAccessToken(componentAppId, componentToken, authorizationCode, parentCallback) {
        WeChatAPI.getAuthorizerAccessToken(componentAppId, componentToken, authorizationCode, function(error, data) {
            if (error) {
                parentCallback(error, null);
            } else {
                if( data.errmsg) {
                    parentCallback(new Error(data.errmsg), null);
                } else {
                    parentCallback(null, data);
                }
            }
        });
    }

    /**
     * Create QR Code for appId
     * 
     * @name createQRCode
     * @param {String} appId - The application id of official account
     * @param {Object} params - The params to create QR Code
     * 
     * @returns {void}
     */
    createQRCode(appId, params, parentCallback) {
        var me = this;
        this.acquireStorageClients({
            redis: true
        }, function(clients) {
            var redis = clients.redis;

            var clientAuthKey = redisKey.client_auth(appId);

            async.waterfall([
                function(callback) {
                    redis.hgetall(clientAuthKey, callback);
                }, function(data, callback) {
                    me.releaseStorageClients(clients);
                    if( data ) {
                        var accessToken = data.token;
                        callback(null, accessToken);
                    } else {
                        parentCallback(null);
                    }
                }, function(accessToken, callback) {
                    WeChatAPI.createQRCode(accessToken, params, function(error, data) {
                        if (error || data && data.errmsg) {
                            me.logger.error(error || data && data.errmsg);
                            callback(null, null);
                        } else if (data) {
                            callback(null, data);
                        }
                    });
                }, function(data) {
                    parentCallback(data);
                }
            ]);
        }, function(error) {
            parentCallback(error);
        });
    }

    /**
     * 
     * Get QRCode url
     * 
     * @name getQRCodeURL
     * @param {String} ticket - The QR Code ticket
     * 
     * @returns {String}
     * @memberOf WeChatAPI
     */
    getQRCodeURL(ticket) {
        return WeChatAPI.getQRCodeURL(ticket);
    }

    /**
     * Get WeChat user information
     * 
     * @name getWeChatUserInfo
     * @param {String} accessToken - The access token of WeChat offical account
     * @param {String} openId - The open id of WeChat user
     * @param {String} accountType - The account auhtorize type, value is: normal or oauth
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    getWeChatUserInfo(accessToken, openId, accountType, parentCallback) {
        WeChatAPI.getWeChatUserInfo(accessToken, openId, accountType, (data) => {
            parentCallback(data);
        });
    }

    /**
     * Get WeChat user information by app_id
     * 
     * @name getWeChatUserInfo
     * @param {String} appId - The appId of WeChat offical account
     * @param {String} openId - The open id of WeChat user
     * @param {String} accountType - The account auhtorize type, value is: normal or oauth
     * @param {Function} parentCallback - The callback function
     * 
     * @returns {void}
     */
    getWeChatUserInfoByAppId(appId, openId, accountType, parentCallback) {
        var me = this;

        me.acquireStorageClients({
            redis: true
        }, function(clients) {
            var redis = clients.redis;

            async.waterfall([
                (callback) => {
                    var clientAuthKey = redisKey.client_auth(appId);
                    redis.hgetall(clientAuthKey, callback);
                }, (authInfo, callback) => {
                    if (authInfo) {
                        var accessToken = authInfo.token;
                        me.getWeChatUserInfo(accessToken, openId, accountType, (data) => {
                            callback(null, data);
                        })
                    } else {
                        parentCallback({});
                    }
                }, (data) => {
                    me.releaseStorageClients(clients);
                    parentCallback(data);
                }
            ]);

        }, function(error) {
            //TODO: Add log here
            parentCallback({});
        });
    }
}

exports.WeChatService = WeChatService;